package org.gap.wizards.world;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.gap.wizards.Drawable;
import org.gap.wizards.creature.Creature;
import org.gap.wizards.items.Item;
import org.gap.wizards.pc.PlayerCharacter;
import org.gap.wizards.utils.Persistence;
import org.gap.wizards.views.ViewPlatform;
import org.gap.wizards.views.ViewPlatform.Color;

public abstract class CommonLocation implements Location {
	private static final String CLASS = "class";

	private static final String BUILDING = "building";
	private static final String CREATURE = "creature";
	private static final String TRANSITION = "transition";

	private static final String ITEM = "item.";
	private static final String NUMBER_OF_ITEMS = "number.of.items";

	protected int width, height;

	protected List<ClosedSpace> buildings;
	protected Map<Item, ItemsDrawable> items;
	protected List<Creature> creatures;
	protected List<MapTransition> transitions;
	protected Point pc;
	private PlayerCharacter player;

	public CommonLocation() {
		pc = new Point(0, 0);
		buildings = new ArrayList<ClosedSpace>();
		items = new HashMap<Item, ItemsDrawable>();
		creatures = new ArrayList<Creature>();
		transitions = new ArrayList<MapTransition>();
	}

	protected void tryMove(Point moveTo) {
		if (canMoveTo(moveTo)) {
			pc.setLocation(new Point(moveTo.x, moveTo.y));
		}
	}
	
	@Override
	public boolean canMoveTo(Point moveTo) {
		if (isBlockedByCreatures(moveTo)) {
			return false;
		}
		return !isBlocked(moveTo);
	}
	
	@Override
	public boolean isBlocked(Point point) {
		return intersectsBuildings(point);
	}

	protected boolean isBlockedByCreatures(Point moveTo) {
		if (moveTo.equals(pc)) {
			return true;
		}
		for (Creature each : creatures) {
			if (each.getLocation().equals(moveTo)) {
				return true;
			}
		}
		return false;
	}

	protected boolean intersectsBuildings(Point moveTo) {
		for (ClosedSpace each : buildings) {
			if (!each.canMoveTo(moveTo)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public void draw(ViewPlatform console) {
		drawPermanentFixtures(console);

		drawItemsAndCreatures(console);
	}

	protected void drawPermanentFixtures(ViewPlatform console) {
		for (ClosedSpace each : buildings) {
			each.draw(console);
		}
		for (MapTransition each : transitions) {
			if (shouldDraw(each)) {
				each.draw(console);
			}
		}
	}

	protected void drawItemsAndCreatures(ViewPlatform console) {
		for (Drawable each : items.values()) {
			if (shouldDraw(each)) {
				each.draw(console);
			}
		}
		
		for (Creature each : creatures) {
			if (shouldDraw(each)) {
				each.draw(console);
			}
		}
		
		console.print(pc.x, pc.y, Color.WHITE, "@");
	}

	protected abstract boolean shouldDraw(Drawable each);

	protected int distanceFromCoordinate(int x, int y) {
		return (int)new Point(x, y).distance(pc);
	}
	
	@Override
	public void setDimensions(Dimension dimensions) {
		this.width = dimensions.width;
		this.height = dimensions.height;
	}

	@Override
	public Dimension getDimensions() {
		return new Dimension(width, height);
	}

	@Override
	public Point getCharacterLocation() {
		return pc;
	}
	
	@Override
	public void setPlayerCharacter(PlayerCharacter player) {
		this.player = player;
	}
	
	@Override
	public PlayerCharacter getPlayerCharacter() {
		return player;
	}

	public void setCharacterLocation(Point readPoint) {
		pc = readPoint;
	}

	public void addClosedSpace(int x, int y, int width, int height, Point door) {
		ClosedSpace building = new ClosedSpace(new Rectangle(x, y, width,
				height), door);
		buildings.add(building);
	}

	@Override
	public List<Object> getAllFromLocation(Point location) {
		List<Object> results = new LinkedList<Object>();
		for (ClosedSpace each : buildings) {
			if (each.contains(location)) {
				results.add(each);
			}
		}
		for (ItemsDrawable each : items.values()) {
			if (each.isPlacedAt(location)) {
				results.add(each.getItem());
			}
		}
		for (Creature each : creatures) {
			if (each.getLocation().equals(location)) {
				results.add(each);
			}
		}
		for (MapTransition each : transitions) {
			if (each.isAtPoint(location)) {
				results.add(each);
			}
		}
		return results;
	}

	@Override
	public void removeItem(Item toRemove) {
		items.remove(toRemove);
	}

	@Override
	public void addItem(Item item) {
		Point location = pc.getLocation();
		addItemAt(location, item);
	}
	
	@Override
	public void addItemAt(Point location, Item item) {
		items.put(item, new ItemsDrawable(location.x, location.y, item));
	}
	
	@Override
	public void addTransition(MapTransition mapTransition) {
		transitions.add(mapTransition);
	}

	@Override
	public boolean addCreature(Creature creature) {
		return creatures.add(creature);
	}
	
	@Override
	public void removeCreature(Creature creature) {
		creatures.remove(creature);
	}

	@Override
	public void update() {
		for (int i = 0; i < creatures.size(); i++) {
			creatures.get(i).update(this);
		}
	}
	
	@Override
	public void persist(Persistence persistence) {
		persistence.set("dimensions", getDimensions());
		persistence.set("character", getCharacterLocation());

		persistence.set(BUILDING, buildings);
		persistence.set(CREATURE, creatures);
		persistence.set(TRANSITION, transitions);

		persistence.set(NUMBER_OF_ITEMS, items.size());
		int number = 0;
		for (ItemsDrawable each : items.values()) {
			persistence.beginSection(ITEM + (number++));
			each.persist(persistence);
			persistence.endSection();
		}
	}
	
	@Override
	public void load(Persistence persister) {
		setDimensions(persister.getDimension("dimensions"));
		Point pcPoint = persister.getPoint("character");
		if (pcPoint != null) {
			setCharacterLocation(pcPoint);
		}

		buildings = persister.<ClosedSpace>getList(BUILDING);
		creatures = persister.<Creature>getList(CREATURE);
		transitions = persister.<MapTransition>getList(TRANSITION);
		
		int numberOfItems = persister.getInt(NUMBER_OF_ITEMS);
		for (int i = 0; i < numberOfItems; i++) {
			persister.beginSection(ITEM + (i));
			ItemsDrawable itemsDrawable = new ItemsDrawable();
			itemsDrawable.load(persister);
			items.put(itemsDrawable.item, itemsDrawable);
			persister.endSection();
		}
	}

	@Override
	public boolean equals(Object obj) {
		CommonLocation other = (CommonLocation) obj;
		return getDimensions().equals(other.getDimensions())
				&& buildings.size() == other.buildings.size()
				&& items.size() == other.items.size()
				&& getCharacterLocation().equals(other.getCharacterLocation())
				&& transitions.size() == other.transitions.size()
				&& creatures.size() == other.creatures.size();
	}

	public class ItemsDrawable implements Drawable {
		private Point location;
		Item item;

		public ItemsDrawable(int x, int y, Item item) {
			location = new Point(x, y);
			this.item = item;
		}

		public ItemsDrawable() {
		}
		
		@Override
		public int getX() {
			return location.x;
		}
		
		@Override
		public int getY() {
			return location.y;
		}

		public boolean equalsItem(Item matchingItem) {
			return matchingItem.equals(item);
		}

		public Object getItem() {
			return item;
		}

		public Item takeItems() {
			Item result = item;
			item = null;
			return result;
		}

		public boolean isPlacedAt(Point location) {
			return location.distance(this.location) == 0;
		}

		public boolean placedAt(int x, int y) {
			return location.x == x && location.y == y;
		}

		@Override
		public void draw(ViewPlatform console) {
			item.draw(console, location.x, location.y);
		}

		public void persist(Persistence persister) {
			persister.set(CLASS, item.getClass());
			persister.beginSection("item");
			{
				item.persist(persister);
				persister.set("location", location);
			}
			persister.endSection();
		}

		public void load(Persistence persister) {
			item = (Item) persister.getClass(CLASS);
			persister.beginSection("item");
			{
				item.load(persister);
				location = persister.getPoint("location");
			}
			persister.endSection();
		}
	}
}
